home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Libraries / Utilities / Utilities.p < prev   
Encoding:
Text File  |  1994-05-05  |  40.4 KB  |  1,185 lines  |  [TEXT/R*ch]

  1. {Utilities unit ©1994 by Sean Crist (kurisuto@chopin.udel.edu).  (Soon I'll have a new address on some machine}
  2. {in the domain upenn.edu; try reaching me there if chopin.udel.edu doesn't work.) I'd appreciate any comments or }
  3. {bug reports.  This code may be freely used for any purpose, except that military use is expressly prohibited.}
  4.  
  5. {About this unit:}
  6. {This is a real grab bag of utility routines which I've written and find handy; most are routines I think should have}
  7. {been a part of the Toolbox.}
  8.  
  9. {Most of the documentation is in amidst the interface declarations.  I know these are very densely written and }
  10. {look a little daunting, but I encourage you to read through them; you may find some things to save you coding time.}
  11.  
  12. unit Utilities;
  13.  
  14. interface
  15.  
  16. {Mathematical operations on rectangles and points.}
  17.  
  18. {The following two routines are covert a rectangle back and forth between local and global coordinates.}
  19.     procedure LocalToGlobalRect (var TheRect: Rect);
  20.     procedure GlobalToLocalRect (var TheRect: Rect);
  21. {CenterRect moves the first rectangle, centering it inside the second rectangle.}
  22.     procedure CenterRect (var MoveRect: Rect; StillRect: Rect);
  23. {RectCenter figures out what the center point of a rectangle is.}
  24.     function RectCenter (TheRect: Rect): Point;
  25. {MoveRgnTo moves a region so that its upper left corner is the same as that of ToRect.  MoveRgnTo}
  26. {does no scaling; if the rect containing WhichRgn isn't the same size as ToRect, then the lower right}
  27. {corners won't match.}
  28.     procedure MoveRgnTo (WhichRgn: RgnHandle; ToRect: Rect);
  29.  
  30.  
  31. {Operations on strings}
  32.  
  33. {ParamString inserts strings into another string, just as ParamText inserts text into dialogs. For example, }
  34. {ParamString could insert the string 'My Filename' into the string 'Save changes to “^0” before closing?'}
  35. {This is handy if you want to keep alert messages in STR# resources rather than have a separate alert for }
  36. {everything, but still want to be able to insert text.}
  37. {MainString is the string containing the ^0, ^1, ^2, ^3 placeholders; the others are respectively inserted into MainString.}
  38.     procedure ParamString (var MainString: Str255; FirstParam, SecondParam, ThirdParam, FourthParam: Str255);
  39.  
  40. {EllipsisString truncates strings like 'Really obnoxiously long filename' to 'Really obnox…'}
  41. {It takes a string and a pixel width.  Given the text font, size etc. of the current port, EllipsisString}
  42. {trucates the string and puts ellipses (…) at the end so that the total string is not wider than PixelWidth.  If}
  43. {SourceString is already shorter than PixelWidth, EllipsisString just returns SourceString intact.}
  44.     function EllipsisString (SourceString: Str255; PixelWidth: Integer): Str255;
  45.  
  46.  
  47. {The following routines create packed lists of strings in the same format of STR# resources. (I've}
  48. {never actually tested to make sure you can create STR# resources this way, but I believe you could.)}
  49. {This is handy for doing things like making a symbol table when parsing text.}
  50.  
  51. {This function creates a new empty STR.}
  52.     function CreateNewSTR (var TheSTR: Handle): Boolean;
  53. {This function adds a new string to the end of a STR and returns its index}
  54.     function AddToSTR (TheSTR: Handle; TheString: string): Integer;
  55. {This function searches for a given string and returns its index.}
  56.     function FindInSTR (TheSTR: Handle; TheString: string): Integer;
  57. {This function, given an index, returns a string.}
  58.     function ExtractSTR (TheSTR: Handle; TheIndex: Integer): Str255;
  59.  
  60.  
  61.  
  62. {Relatively low-level routines for posting an alert.}
  63.  
  64. {MiscAlert posts the string AlertString in an alert that just has an OK button.}
  65.     procedure MiscAlert (AlertString: Str255);
  66. {MiscInquiry posts AlertString in an alert that has an OK button and a cancel button.}
  67. {It returns the number of the button pushed.}
  68.     function MiscInquiry (AlertString: Str255): integer;
  69. {MiscAlertSTR is same as MiscAlert, but we take the ID and index into a STR# resource and display that string.}
  70.     procedure MiscAlertSTR (rsrcID, index: Integer);
  71. {MiscInquirySTR is same as MiscInquiry, but we take the ID and index into a STR# resource and display that string.}
  72.     function MiscInquirySTR (rsrcID, index: Integer): integer;
  73.  
  74. {These are the resource IDs of the ALRTs used by MiscAlert and MiscInquiry.  These should both contain}
  75. {a large static text item with the text '^0' for ParamText, plus an appropriate icon.}
  76.     const
  77.         OkAlert = 128; {Has an OK button}
  78.         OkAndCancelAlert = 129; {Has an OK button and a Cancel button.}
  79.  
  80.  
  81.  
  82. {Error checking and reporting}
  83.  
  84. {doOSErr is our lowest-level error reporting routine.  It takes any OS error code and displays an appropriate alert.}
  85. {We get the error text from a STR# resource; if the error is one for which we have no STR# entry, we just}
  86. {say something like 'An unexpected error has occurred' plus the error code.  (You should edit this routine}
  87. {to customize it for your application and should create an appropriate STR# resource.)}
  88.     procedure doOSErr (result: OSErr);  {Post an error message.}
  89.  
  90. {The following group of error-checking routines are all written with the same aim: to make other code}
  91. {as sturdy but concise as possible by relieving the code of most of its error-checking work. In every routine }
  92. {which can fail, I define a boolean OkSoFar which is initially set to TRUE; then I write the rest of the routine }
  93. {in paragraphs like the following:}
  94. {                                                                                                                    }
  95. {                if OkSoFar then                                                                                    }
  96. {                    begin                                                                                            }
  97. {                        SomeMemoryManagerCallWhichCanFail;                                                }
  98. {                        OkSoFar := TestMemErr;    {or whatever is appropriate}
  99. {                    end;                                                                                            }
  100. {                                                                                                                    }
  101. {The following group of routines are all consonant with this coding style, each checking for a certain kind}
  102. {of error and returning the boolean FALSE if an error was detected.}
  103.  
  104. {TestMemErr checks the MemErr global and returns TRUE if the error is 0 (no error).  If there is an error,}
  105. {TestMemErr calls doOSErr to post an appropriate alert and returns FALSE.  (The calling routine thus sets}
  106. {its OkSoFar variable to FALSE, and falls through without doing anything else.)}
  107.     function TestMemErr: Boolean;
  108. {TestResErr is same an TestMemErr but checks for resource errors instead.}
  109.     function TestResErr: Boolean;
  110. {TestNilRsrc makes sure a handle is a good one, returning FALSE is the handle is nil.}
  111.     function TestNilRsrc (TheRsrc: Handle): Boolean;
  112.     const
  113.         GotNilResource = 7000;  {A private 'OS error' which we use to mean that GetResource got a nil handle.}
  114. {An obnoxious feature of the Toolbox is that if you try to GetResource a non-existant resource, there will}
  115. {be no error returned, and you just get a nil handle.  GetResOK both checks for an operating system}
  116. {error (TestResErr) AND checks to make sure the resource handle you got back isn't nil (TestNilRsrc).}
  117.     function GetResOK (TheRsrc: Handle): Boolean;
  118. {TestOSResult is for Toolbox calls which are functions returning an OS result, so you can write paragraphed}
  119. {code of the form above with lines like OkSoFar := TestOSResult(SomeToolboxFunctionReturningAnOSresult)}
  120.     function TestOSResult (Result: Integer): Boolean;
  121. {You can call Preflight before undertaking some memory-hungry operation to make sure you have enough}
  122. {memory to complete it.  MemNeeded is the minimum amount of memory needed to complete the operation;}
  123. {Preflight returns TRUE if there is enough.  If there isn't enough, Preflight returns FALSE and also puts up an}
  124. {alert with a message of the sort 'There isn't enough memory to do X'.  FailStringIndex is an index to a STR#}
  125. {resource containing all the failure messages for Preflight alerts; you'd want one such string for each}
  126. {kind of operation.}
  127.     function Preflight (MemNeeded: LongInt; FailStringIndex: Integer): Boolean;
  128.  
  129.  
  130.  
  131.  
  132. {Routines having to do with dialogs}
  133.  
  134. {My strategy for drawing user items in modeless dialogs is as follows.  When I open the dialog, I call LinkUserItem}
  135. {on each user item in the dialog.  This sets the pointer for the update routines for these items all to one routine,}
  136. {UpdateUserItem, defined below.  When the dialog gets an update event, the dialog manager will thus call my }
  137. {UpdateUserItem procedure to have to user item redrawn.  UpdateUserItem in turn calls an external routine }
  138. {which you'll have to write yourself; your routine should redraw the item.}
  139.     procedure LinkUserItem (WhichDialog: DialogPtr; WhichItem: Integer);
  140.  
  141. {The following routines are all abbreviations for GetDItem or SetDItem calls.  For example, if you only want the}
  142. {rect of a particular dialog item, and you want to use GetDItem,  you'd usually have to have DummyHandle and}
  143. {DummyText variables as well.}
  144. {The following routines can make your code more concise, giving you just the information you need about a dialog}
  145. {item and nothing more.}
  146. {GetDRect returns the rect of a dialog item.}
  147.     function GetDRect (theDialog: DialogPtr; theItem: Integer): Rect;
  148. {GetDControl returns a ControlHandle for a dialog item which is a control.}
  149.     function GetDControl (theDialog: DialogPtr; theItem: Integer): ControlHandle;
  150. {GetDHandle returns the handle for a dialog item.}
  151.     function GetDHandle (theDialog: DialogPtr; theItem: Integer): Handle;
  152. {SetDText changes the text of a dialog item.}
  153.     procedure SetDText (theDialog: DialogPtr; theItem: Integer; theString: Str255);
  154. {SetDRect changes the rect of a dialog item.  Note: if you are moving a dialog item, it's up to you to InvalRect the}
  155. {old rect and the new rect.}
  156.     procedure SetDRect (theDialog: DialogPtr; theItem: Integer; theRect: Rect);
  157.  
  158.  
  159. {Routines having to do with the cursor}
  160.  
  161. {Call InitWatch when your program starts up to set the global WatchHandle to the watch cursor.}
  162.     procedure InitWatch;
  163. {ShowWatch is just like InitCursor, except that it changes the cursor to a plain watch.}
  164.     procedure ShowWatch;
  165. {The next three routines are for an animated spinning watch.  Call StartSpinningWatch to load the}
  166. {watch cursors into memory (you must add these resources to your file, of course); call StopSpinningWatch}
  167. {watch when you're finished with the spinning watch to deallocate the memory allocated by StartSpinningWatch.}
  168. {In between, call SpinWatch as often as possible.  SpinWatch keeps track of the ticks, and keeps the watch}
  169. {spinning at a constant speed, regardless of how often you call it.  Don't call SpinWatch unless you've called}
  170. {StartSpinningWatch first!}
  171.     procedure StartSpinningWatch;
  172.     procedure SpinWatch;
  173.     procedure StopSpinningWatch;
  174.  
  175. {Variables used by the above routines.}
  176.     var
  177.         WatchHandle: CursHandle;
  178.         SpinningWatchHandle: array[1..7] of CursHandle;
  179.         SpinningWatchTimer: LongInt;
  180.         SpinningWatchState: Integer;
  181.  
  182.  
  183.  
  184. {Routines for drawing pop-up menus.  Yeah, I know there's a pop-up CDEF in System 7, but what if you still want to}
  185. {support earlier systems?}
  186.  
  187. {Call InitPopUpTriangle before calling DrawPopUpMenu.  InitPopUpTriangle creates a region for the down-triangle.}
  188. {You could call this from your program's initialize procedure.}
  189.     procedure InitPopUpTriangle;
  190. {DrawPopupMenu draws a popup menu with a box, drop-shadow, and down-pointing triangle in TheRect.}
  191. {The TitleString is drawn into the menu box.  EllipsisString is called to truncate TitleString in case it is too long.}
  192.     procedure DrawPopupMenu (StartRect: Rect; TitleString: Str255);
  193. {RenamePopupMenu is like DrawPopupMenu, but it only redraws the name of the menu. If you're changing the text }
  194. {in the menu after the user has made a selection, it's better to call RenamePopupMenu to avoid the flicker of }
  195. {redrawing the whole menu.}
  196.     procedure RenamePopupMenu (TheRect: Rect; TitleString: Str255);
  197. {DoDialogPopUp is handy when you've gotten a mousedown in a popup menu in a dialog, detected by your filter function.}
  198. {WhichDialog is the dialog where the mouse hit the menu.  TitleDItem is the title of the menu, a static text item; it will }
  199. {be appropriately inverted.  PopUpDItem is the dialog item which is the menu itself (a user item).  StartSelection is the }
  200. {item in TheMenu which should appear right above PopUpDItem.  TheMenu is the menu you want to pop up.  DoDialogPopUp }
  201. {returns the menu item which was selected.}
  202.     function DoDialogPopUp (WhichDialog: DialogPtr; TitleDItem, PopUpDItem, StartSelection: Integer; theMenu: MenuHandle): Integer;
  203.  
  204.  
  205. {List Manager routines}
  206.  
  207. {The following are a simplification of the List Manager.  These routines are for making a list of text items.}
  208. {CreateList creates a new list with no elements.}
  209.     procedure CreateList (var TheListHandle: ListHandle; TheWindow: WindowPtr; TheRect: Rect);
  210. {UpdateList redraws a list in response to an update event.}
  211.     procedure UpdateList (TheListHandle: ListHandle);
  212. {DoClick handles a click in the list rectangle.  If it was a double-click, we will return TRUE.}
  213.     function DoClick (TheListHandle: ListHandle; TheWhere: Point): Boolean;
  214. {TurnOffSelection turns off any hilited item.}
  215.     procedure TurnOffSelection (TheListHandle: ListHandle);
  216. {ListSelection returns the string currently selected; empty string if no selection.}
  217.     function ListSelection (TheListHandle: ListHandle): string;
  218. {AddCell adds a new cell, setting its text to NewString.}
  219.     procedure AddCell (TheListHandle: ListHandle; NewString: str255);
  220. {RenameCell changes the name of an existing cell}
  221.     procedure RenameCell (TheListHandle: ListHandle; OldString, NewString: Str255);
  222. {DeleteCell removes the cell with the given name from the list.}
  223.     procedure DeleteCell (TheListHandle: ListHandle; TheString: string);
  224. {DisposList gets rid of the list when we're done with it, cleaning up all the memory.}
  225.     procedure DisposList (TheListHandle: ListHandle);
  226.  
  227.  
  228. {To make a little up-down arrow control like that in the Alarm Clock desk accessory, create a dialog item}
  229. {which is a PICT of the arrow control.  Check for mousedowns in this PICT in your filter function; if you get}
  230. {such a mousedown, call ArrowClick.  ArrowClick acts very much like TrackControl, and returns one of the}
  231. {constants below depending on where the user clicked. (This isn't a true control, of course, but it acts like one.)}
  232.     function ArrowClick (ClickWhere: Point; ArrowRect: Rect): Integer;
  233.     const
  234.         ArrowNone = 0;  {ArrowNone is returned if the user dragged the mouse out of the arrow before releasing the button.}
  235.         ArrowUp = 1;
  236.         ArrowDown = 2;
  237.  
  238.  
  239.  
  240.  
  241. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  242.  
  243. implementation
  244.  
  245.  
  246.     procedure LocalToGlobalRect (var TheRect: Rect);
  247.     begin
  248.         LocalToGlobal(TheRect.TopLeft);
  249.         LocalToGlobal(TheRect.BotRight);
  250.     end;
  251.  
  252.     procedure GlobalToLocalRect (var TheRect: Rect);
  253.     begin
  254.         GlobalToLocal(TheRect.TopLeft);
  255.         GlobalToLocal(TheRect.BotRight);
  256.     end;
  257.  
  258.     procedure CenterRect;
  259.         var
  260.             MoveVCenter, MoveHCenter, StillVCenter, StillHCenter: Integer;
  261.     begin
  262.         MoveVCenter := MoveRect.left + MoveRect.right;
  263.         MoveVCenter := MoveVCenter div 2;
  264.         MoveHCenter := MoveRect.top + MoveRect.bottom;
  265.         MoveHCenter := MoveHCenter div 2;
  266.         StillVCenter := StillRect.left + StillRect.right;
  267.         StillVCenter := StillVCenter div 2;
  268.         StillHCenter := StillRect.top + StillRect.bottom;
  269.         StillHCenter := StillHCenter div 2;
  270.         OffsetRect(MoveRect, StillVCenter - MoveVCenter, StillHCenter - MoveHCenter);
  271.     end;
  272.  
  273. {Figure out where the center point of a rectangle is.}
  274.     function RectCenter;
  275.         var
  276.             TempPoint: Point;
  277.     begin
  278.         TempPoint.h := (TheRect.Right + TheRect.Left) div 2;
  279.         TempPoint.v := (TheRect.Top + TheRect.Bottom) div 2;
  280.         RectCenter := TempPoint;
  281.     end;
  282.  
  283.  
  284.     procedure MoveRgnTo (WhichRgn: RgnHandle; ToRect: Rect);
  285.         var
  286.             verticalOffset, horizontalOffset: Integer;
  287.     begin
  288.         verticalOffset := ToRect.top - WhichRgn^^.RgnBBox.top;
  289.         horizontalOffset := ToRect.left - WhichRgn^^.RgnBBox.left;
  290.         OffsetRgn(WhichRgn, horizontalOffset, verticalOffset);
  291.     end;
  292.  
  293.  
  294. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  295.  
  296.  
  297. {The following routine is similar to the Toolbox routine ParamText;  it takes a}
  298. {string and inserts other strings into it.  I am writing this routine because}
  299. {I prefer to put my alert messages in STR# resources rather than making a}
  300. {slew of different ALRT resources.  This way, I can insert information into}
  301. {the strings myself, and then put the finished string into the alert using ParamText.}
  302.     procedure ParamString;
  303.         var
  304.             counter: Integer;
  305.             NewString: Str255;
  306.             GotCaret: Boolean;
  307.     begin
  308.         GotCaret := false;
  309.         NewString := '';
  310.         for counter := 1 to length(MainString) do
  311. {If we got a caret last time through the loop, it might be ^0, ^1, ^2, or ^3, which}
  312. {means we've got to insert the appropriate text.  So we look at the next}
  313. {character and decide what to insert.}
  314.             if GotCaret then
  315.                 begin
  316.                     GotCaret := false;  {Get ready for next time.}
  317.                     if MainString[counter] = '0' then
  318.                         NewString := Concat(NewString, FirstParam)
  319.                     else if MainString[counter] = '1' then
  320.                         NewString := Concat(NewString, SecondParam)
  321.                     else if MainString[counter] = '2' then
  322.                         NewString := Concat(NewString, ThirdParam)
  323.                     else if MainString[counter] = '3' then
  324.                         NewString := Concat(NewString, FourthParam)
  325.                 end
  326. {But if we didn't get a caret last time through the loop, see if we've got one this}
  327. {time.}
  328.             else if MainString[counter] = '^' then
  329.                 GotCaret := true
  330. {But if it isn't a caret, then just copy the character into the new string.}
  331.             else
  332.                 NewString := Concat(NewString, MainString[counter]);
  333.         MainString := NewString;
  334.     end;
  335.  
  336.  
  337.     function EllipsisString (SourceString: Str255; PixelWidth: Integer): Str255;
  338.         var
  339.             Ellipsis: Str255;
  340.             EllipsisLength: Integer;
  341.             TargetLength: Integer;
  342.             CumulativeString: Str255;
  343.             Counter: Integer;
  344.             done: Boolean;
  345.     begin
  346. {We assume that the appropriate port has been set, and then use the font and size for that}
  347. {port to do our calculation.}
  348.         if StringWidth(SourceString) < PixelWidth then
  349.             EllipsisString := SourceString
  350.         else
  351.             begin
  352.                 Ellipsis := '…';
  353.                 EllipsisLength := StringWidth(Ellipsis);
  354.                 TargetLength := PixelWidth - EllipsisLength;
  355.                 CumulativeString := '';
  356.                 Counter := 0;
  357.                 done := false;
  358.                 while not done do
  359.                     begin
  360.                         if StringWidth(Concat(CumulativeString, SourceString[Counter])) >= TargetLength then
  361.                             done := true
  362.                         else
  363.                             begin
  364.                                 Counter := Counter + 1;
  365.                                 CumulativeString := Concat(CumulativeString, SourceString[Counter]);
  366.                             end;
  367.                     end;
  368.                 EllipsisString := Concat(CumulativeString, Ellipsis);
  369.             end;
  370.     end;
  371.  
  372.  
  373. {About the following four routines:  these routines are for compressing strings into}
  374. {an array with the same structure as a STR# resource.  This is a way to save a lot}
  375. {of memory which would go wasted if we stored lists of strings as arrays of Str255.}
  376.  
  377.     function CreateNewSTR (var TheSTR: Handle): Boolean;
  378.         var
  379.             TheNewSTR: Handle;
  380.     begin
  381.         TheNewSTR := NewHandle(2);   {Make a new handle the size of an integer.}
  382.         if MemError <> 0 then   {Check for MemError like good boys and girls}
  383.             begin
  384.                 doOSErr(MemError);
  385.                 CreateNewStr := false;   {Tell whoever called us that we've failed.}
  386.             end
  387.         else   {The memory was successfully allocated, so go ahead.}
  388.             begin
  389.                 CreateNewStr := true;  {Tell whoever called us that we've succeeded.}
  390.                 TheSTR := TheNewSTR;
  391.                 StuffHex(TheNewSTR^, '0000');  {Initialize the number of strings to 0.}
  392.             end;
  393.     end;
  394.  
  395. {The following routine makes odd numbers into even numbers by adding one if necessary.}
  396.     function MakeEven (OddInteger: Integer): Integer;
  397.     begin
  398. {If this is an even integer...}
  399.         if OddInteger = ((OddInteger div 2) * 2) then
  400. {...then just return the number we were given...}
  401.             MakeEven := OddInteger
  402.         else
  403. {...but if this is an odd integer, add 1 to make it even.}
  404.             MakeEven := OddInteger + 1;
  405.     end;
  406.  
  407. {This function adds a new string to the end of a STR and returns its index.  If memory is}
  408. {insufficient, we return -1.}
  409.     function AddToSTR; {(TheSTR: Handle, TheString: string): Integer;}
  410.         var
  411.             OldNumberOfStrings, NewNumberOfStrings: Integer;
  412.             NewStringEvenLength, StringLength: Byte;
  413.             counter, CurrentOffset: Integer;
  414.             ScratchPtr: Ptr;
  415.             OldSize: LongInt;
  416.             OkSoFar: Boolean;
  417.     begin
  418.         OkSoFar := true;  {Let's assume everything's going to be all right.}
  419. {Figure out how big the old handle and the new string are.}
  420.         NewStringEvenLength := MakeEven(Length(TheString) + 1);  {+1 for size byte}
  421.         OldSize := GetHandleSize(TheSTR);
  422. {Set the size of the handle and check for errors.}
  423.         SetHandleSize(TheSTR, OldSize + NewStringEvenLength);
  424.         OKSoFar := TestMemErr;
  425. {If everything is OK, then copy the new string into the handle.}
  426.         if OkSoFar then
  427.             begin
  428.                 HLock(TheSTR);
  429.  
  430. {Figure out where the current end of the block is.}
  431.                 BlockMove(TheSTR^, @OldNumberOfStrings, 2);
  432.                 CurrentOffset := 2;
  433.                 if OldNumberOfStrings > 0 then
  434.                     for counter := 1 to OldNumberOfStrings do
  435.                         begin
  436.                             ScratchPtr := Ptr(LongInt(TheSTR^) + CurrentOffset);
  437.                             StringLength := ScratchPtr^;
  438.                             StringLength := MakeEven(StringLength + 1);
  439.                             CurrentOffset := CurrentOffset + StringLength;
  440.                         end;
  441.  
  442. {Copy the string into there.}
  443.                 ScratchPtr := Ptr(LongInt(TheSTR^) + CurrentOffset);
  444.                 BlockMove(@TheString, ScratchPtr, NewStringEvenLength);
  445.  
  446. {Update the count of strings.}
  447.                 NewNumberOfStrings := OldNumberOfStrings + 1;
  448.                 BlockMove(@NewNumberOfStrings, TheSTR^, 2);
  449.  
  450.                 HUnlock(TheSTR);
  451.             end;
  452.  
  453.         AddToSTR := NewNumberOfStrings;
  454.         if not OkSoFar then
  455.             AddToSTR := -1;
  456.     end;
  457.  
  458. {This function searches for a given string and returns its index.  If we can't find it,}
  459. {we return 0.}
  460.     function FindInSTR; {(TheSTR:  Handle , TheString: string): Integer;}
  461.         var
  462.             Index, CurrentOffset, TopStrings: Integer;
  463.             StringLength: Byte;
  464.             CheckString: Str255;
  465.             done, foundIt: Boolean;
  466.             ScratchPtr: Ptr;
  467.     begin
  468.         Index := 0;
  469.         BlockMove(TheSTR^, @TopStrings, 2);  {Get the number of strings.}
  470.         CurrentOffset := 2;
  471.         done := false;
  472.         foundIt := false;
  473.         if TopStrings > 0 then
  474. {Loop through the strings until we find a match or until we've gone through them all.}
  475.             while not done do
  476.                 begin
  477.                     Index := Index + 1;
  478.                     ScratchPtr := Ptr(LongInt(TheSTR^) + CurrentOffset);
  479.                     StringLength := ScratchPtr^;
  480.                     StringLength := MakeEven(StringLength + 1);
  481.                     BlockMove(ScratchPtr, @CheckString, StringLength);
  482.                     if EqualString(CheckString, TheString, false, true) then
  483.                         begin
  484.                             done := true;
  485.                             foundIt := true;
  486.                         end
  487.                     else
  488.                         begin
  489.                             CurrentOffset := CurrentOffset + StringLength;
  490.                         end;
  491.                     if Index = TopStrings then
  492.                         done := true;
  493.                 end;
  494.         if FoundIt then
  495.             FindInSTR := Index
  496.         else
  497.             FindInStr := 0;
  498.     end;
  499.  
  500. {This function, given an index, returns a string.  If the index is out of range, we}
  501. {return an empty string.}
  502.     function ExtractSTR; {(TheSTR: Handle, TheIndex: Integer): Str255;}
  503.         var
  504.             CurrentOffset, TopStrings, counter: Integer;
  505.             StringLength: Byte;
  506.             TheString: Str255;
  507.             ScratchPtr: Ptr;
  508.     begin
  509.         BlockMove(TheSTR^, @TopStrings, 2);
  510.         CurrentOffset := 2;
  511.         TheString := '';
  512.         if (TopStrings > 0) and (TheIndex <= TopStrings) then
  513.             begin
  514.                 for counter := 1 to TheIndex - 1 do
  515.                     begin
  516.                         ScratchPtr := Ptr(LongInt(TheSTR^) + CurrentOffset);
  517.                         StringLength := ScratchPtr^;
  518.                         StringLength := MakeEven(StringLength + 1);
  519.                         CurrentOffset := CurrentOffset + StringLength;
  520.                     end;
  521.                 ScratchPtr := Ptr(LongInt(TheSTR^) + CurrentOffset);
  522.                 StringLength := ScratchPtr^;
  523.                 StringLength := MakeEven(StringLength + 1);
  524.                 BlockMove(ScratchPtr, @TheString, StringLength);
  525.             end;
  526.         ExtractSTR := TheString;
  527.     end;
  528.  
  529.  
  530.  
  531. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  532.  
  533.  
  534.     procedure MiscAlert (AlertString: Str255);
  535.         var
  536.             result: integer;
  537.     begin
  538.         InitCursor;
  539.         ParamText(AlertString, '', '', '');
  540.         result := Alert(OKAlert, nil);
  541.     end;
  542.  
  543.     function MiscInquiry (AlertString: Str255): integer;
  544.         var
  545.             result: integer;
  546.     begin
  547.         InitCursor;
  548.         ParamText(AlertString, '', '', '');
  549.         result := Alert(OkAndCancelAlert, nil);
  550.         MiscInquiry := result;
  551.     end;
  552.  
  553.     procedure MiscAlertStr (rsrcID, index: Integer);
  554.         var
  555.             AlertString: Str255;
  556.     begin
  557.         GetIndString(AlertString, rsrcID, index);
  558.         MiscAlert(AlertString);
  559.     end;
  560.  
  561.     function MiscInquiryStr (rsrcID, index: Integer): integer;
  562.         var
  563.             result: integer;
  564.             AlertString: Str255;
  565.     begin
  566.         GetIndString(AlertString, rsrcID, index);
  567.         result := MiscInquiry(AlertString);
  568.         MiscInquiryStr := result;
  569.     end;
  570.  
  571.  
  572. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  573.  
  574.     procedure doOSErr;  {This posts an alert for unexpected errors.}
  575.         const
  576.             MyErrorsSTR = 129;
  577.         var
  578.             ResultString: Str255;
  579.             ErrorString: Str255;
  580.             ignore: integer;
  581.     begin
  582.         InitCursor;
  583.         case result of
  584. {You should edit these case selectors and create a STR# resource appropriate for your application.}
  585.             -33, -34: {Disk full}
  586.                 GetIndString(ErrorString, MyErrorsSTR, 2);
  587.             -44, -45, -46, -47:  {File locked or busy.}
  588.                 GetIndString(ErrorString, MyErrorsSTR, 3);
  589.             -41:  {Out of memory. }
  590.                 GetIndString(ErrorString, MyErrorsSTR, 4);
  591.             otherwise  {Huh?  Don't know this error type.  Just post a generic message and the number.}
  592.                 begin
  593.                     GetIndString(ErrorString, MyErrorsSTR, 1);  {"An unexpected error has occurred."}
  594.                     NumToString(result, ResultString);
  595.                     ErrorString := Concat(ErrorString, ResultString);
  596.                 end;
  597.         end;
  598.         ParamText(ErrorString, '', '', '');
  599.         ignore := Alert(1001, nil);  {Our generic stop alert}
  600.     end;
  601.  
  602.  
  603. {This routine allows us to reduce a lot of the coding overhead involved when doing memory}
  604. {operations.}
  605.     function TestMemErr: Boolean;
  606.         var
  607.             OkSoFar: Boolean;
  608.     begin
  609.         OkSoFar := true;
  610.         if MemError <> 0 then
  611.             begin
  612.                 OkSoFar := false;
  613.                 doOSErr(MemError);
  614.             end;
  615.         TestMemErr := OkSoFar;
  616.     end;
  617.  
  618.     function TestResErr: Boolean;
  619.         var
  620.             OkSoFar: Boolean;
  621.     begin
  622.         OkSoFar := true;
  623.         if ResError <> 0 then
  624.             begin
  625.                 OkSoFar := false;
  626.                 doOSErr(ResError);
  627.             end;
  628.         TestResErr := OkSoFar;
  629.     end;
  630.  
  631.     function TestNilRsrc (TheRsrc: Handle): Boolean;
  632.         var
  633.             OkSoFar: Boolean;
  634.     begin
  635.         OkSoFar := true;
  636.         if TheRsrc^ = nil then
  637.             begin
  638.                 OkSoFar := false;
  639.                 doOSErr(GotNilResource);
  640.             end;
  641. {I'm not sure which is right, so I'll do it both ways.}
  642.         if TheRsrc = nil then
  643.             begin
  644.                 OkSoFar := false;
  645.                 doOSErr(GotNilResource);
  646.             end;
  647.         TestNilRsrc := OkSoFar;
  648.     end;
  649.  
  650.  
  651.     function GetResOK (TheRsrc: Handle): Boolean;
  652.         var
  653.             OkSoFar: Boolean;
  654.     begin
  655.         OkSoFar := TestResErr;
  656.  
  657.         if OkSoFar then
  658.             OkSoFar := TestNilRsrc(TheRsrc);
  659.  
  660.         GetResOK := OkSoFar;
  661.     end;
  662.  
  663.  
  664.     function TestOSResult (Result: Integer): Boolean;
  665.         var
  666.             OkSoFar: Boolean;
  667.     begin
  668.         OkSoFar := TRUE;
  669.         if Result <> 0 then
  670.             begin
  671.                 OkSoFar := False;
  672.                 doOSErr(Result);
  673.             end;
  674.         TestOSResult := OkSoFar;
  675.     end;
  676.  
  677.  
  678. {This function determines whether we have enough memory to go ahead with a particular function, such}
  679. {as opening a certain dialog.  MemNeeded is the estimated amount of memory needed to perform this function.}
  680. {FailStringIndex is the index to a STR# resource, which puts up the appropriate message in an alert if we don't}
  681. {have enough memory.}
  682.     function Preflight; {(MemNeeded, FailStringIndex): Boolean;}
  683.         const
  684.             PreflightStrings = 128;
  685.         var
  686.             ignore: integer;
  687.             MessageString: Str255;
  688.             TotalBytes, ContigBytes: LongInt;
  689.     begin
  690.         Preflight := TRUE;  {Let's assume everything is OK.}
  691.         PurgeSpace(TotalBytes, ContigBytes);   {How much memory is there?}
  692.         if MemNeeded > ContigBytes then   {Is that enough memory?}
  693.             begin
  694.                 MiscAlertStr(PreflightStrings, FailStringIndex);  {Put up the alert.}
  695.                 Preflight := FALSE;   {and report back to the calling routine that there's not enough space.}
  696.             end;
  697.     end;
  698.  
  699.  
  700. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  701.  
  702.  
  703. {As noted above, you must write your own doUpdateUserItem routine which redraws the user items}
  704. {in your dialogs.}
  705.     procedure doUpdateUserItem (whichWindow: WindowPtr; Item: Integer);
  706.     external;
  707.  
  708.     procedure UpdateUserItem (whichWindow: WindowPtr; Item: Integer);
  709.     begin
  710.         doUpdateUserItem(whichWindow, Item);
  711.     end;
  712.  
  713.     procedure LinkUserItem (WhichDialog: DialogPtr; WhichItem: Integer);
  714.         var
  715.             IgnoreType: Integer;
  716.             IgnoreHandle: Handle;
  717.             IgnoreRect: Rect;
  718.     begin
  719.         GetDItem(WhichDialog, WhichItem, IgnoreType, IgnoreHandle, IgnoreRect);
  720.         SetDItem(WhichDialog, WhichItem, IgnoreType, @UpdateUserItem, IgnoreRect);
  721.     end;
  722.  
  723.     function GetDRect (theDialog: DialogPtr; theItem: Integer): Rect;
  724.         var
  725.             IgnoreType: Integer;
  726.             IgnoreHandle: Handle;
  727.     begin
  728.         GetDItem(theDialog, theItem, IgnoreType, IgnoreHandle, GetDRect);
  729.     end;
  730.  
  731.     function GetDHandle (theDialog: DialogPtr; theItem: Integer): Handle;
  732.         var
  733.             IgnoreType: Integer;
  734.             IgnoreRect: Rect;
  735.             TempHandle: Handle;
  736.     begin
  737.         GetDItem(theDialog, theItem, IgnoreType, TempHandle, IgnoreRect);
  738.         GetDHandle := TempHandle;
  739.     end;
  740.  
  741.     function GetDControl (theDialog: DialogPtr; theItem: Integer): ControlHandle;
  742.         var
  743.             IgnoreType: Integer;
  744.             IgnoreRect: Rect;
  745.             TempHandle: Handle;
  746.     begin
  747.         GetDItem(theDialog, theItem, IgnoreType, TempHandle, IgnoreRect);
  748.         GetDControl := ControlHandle(TempHandle);
  749.     end;
  750.  
  751.     procedure SetDText (theDialog: DialogPtr; theItem: Integer; theString: Str255);
  752.         var
  753.             IgnoreType: Integer;
  754.             IgnoreRect: Rect;
  755.             TempHandle: Handle;
  756.     begin
  757.         GetDItem(TheDialog, TheItem, IgnoreType, TempHandle, IgnoreRect);
  758.         SetIText(TempHandle, theString);
  759.     end;
  760.  
  761.     procedure SetDRect (theDialog: DialogPtr; theItem: Integer; theRect: Rect);
  762.         var
  763.             IgnoreType: Integer;
  764.             IgnoreRect: Rect;
  765.             IgnoreHandle: Handle;
  766.     begin
  767.         GetDItem(TheDialog, TheItem, IgnoreType, IgnoreHandle, IgnoreRect);
  768.         SetDItem(TheDialog, TheItem, IgnoreType, IgnoreHandle, theRect);
  769.     end;
  770.  
  771.  
  772. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  773.  
  774.     procedure InitWatch;
  775.     begin
  776.         WatchHandle := GetCursor(WatchCursor);
  777.     end;
  778.  
  779.     procedure ShowWatch;
  780. {Set cursor to watch}
  781. {We have no routine ShowPointer, because we can simply say InitCursor.}
  782.     begin
  783.         SetCursor(WatchHandle^^);
  784.     end;
  785.  
  786. {This routine is called when we first start want to display the moving watch cursor.}
  787.     procedure StartSpinningWatch;
  788.         var
  789.             counter: Integer;
  790.     begin
  791.         for counter := 1 to 7 do
  792.             SpinningWatchHandle[counter] := GetCursor(Counter + 256);
  793.         SpinningWatchState := 8;
  794.         SpinningWatchTimer := TickCount;
  795.     end;
  796.  
  797. {This routine is called when we want to rotate the watch to the next position.  We should}
  798. {just call this routine as often as possible; this routine worries about the timing.}
  799.     procedure SpinWatch;
  800.         var
  801.             NewTime: LongInt;
  802.     begin
  803.         NewTime := TickCount;
  804.         if (NewTime - 30) > SpinningWatchTimer then
  805.             begin
  806.                 SpinningWatchState := SpinningWatchState + 1;
  807.                 SpinningWatchTimer := NewTime;
  808.                 if SpinningWatchState > 8 then
  809.                     SpinningWatchState := 1;
  810.                 if SpinningWatchState = 8 then
  811.                     ShowWatch
  812.                 else
  813.                     SetCursor(SpinningWatchHandle[SpinningWatchState]^^);
  814.             end;
  815.     end;
  816.  
  817. {This routine deallocates the memory we used and inits the cursor.}
  818.     procedure StopSpinningWatch;
  819.         var
  820.             counter: Integer;
  821.     begin
  822.         for counter := 1 to 7 do
  823.             ReleaseResource(Handle(SpinningWatchHandle[counter]));
  824.     end;
  825.  
  826.  
  827. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  828.  
  829. {Drawing routines for popup menus.}
  830.  
  831.     var
  832.         TheTriangleRgn: RgnHandle;
  833.  
  834.     procedure InitPopUpTriangle;
  835.     begin
  836.         TheTriangleRgn := NewRgn;
  837.         OpenRgn;
  838.         MoveTo(0, 0);
  839.         LineTo(14, 0);
  840.         LineTo(7, 7);
  841.         LineTo(0, 0);
  842.         CloseRgn(TheTriangleRgn);
  843.     end;
  844.  
  845.     procedure DrawPopupMenu (StartRect: Rect; TitleString: Str255);
  846.         var
  847.             TrianglePict: PicHandle;
  848.             TriangleRect: Rect;
  849.             TriangleRgnRect: Rect;
  850.             MenuRect: Rect;
  851.     begin
  852.         MenuRect := StartRect;
  853.         MenuRect.right := MenuRect.right - 1;
  854.         MenuRect.bottom := MenuRect.bottom - 1;
  855. {Frame the rect.}
  856.         frameRect(MenuRect);
  857. {Draw the drop shadow.}
  858.         MoveTo(MenuRect.Right, MenuRect.Top + 1);
  859.         LineTo(MenuRect.Right, MenuRect.Bottom);
  860.         LineTo(MenuRect.Left + 1, MenuRect.Bottom);
  861. {Draw the down arrow.}
  862.         TriangleRgnRect := TheTriangleRgn^^.RgnBBox;
  863.         TriangleRect.Top := MenuRect.Top + 4;
  864.         TriangleRect.Bottom := TriangleRect.Top + (TriangleRgnRect.Bottom - TriangleRgnRect.Top);
  865.         TriangleRect.Right := MenuRect.Right - 5;
  866.         TriangleRect.Left := TriangleRect.Right - (TriangleRgnRect.Right - TriangleRgnRect.Left);
  867.  
  868.         MoveRgnTo(TheTriangleRgn, TriangleRect);
  869.         PaintRgn(TheTriangleRgn);
  870.  
  871. {Draw the title, truncating it and adding ellipses if necessary.}
  872.         RenamePopupMenu(StartRect, TitleString);
  873.     end;
  874.  
  875.     procedure RenamePopupMenu (TheRect: Rect; TitleString: Str255);
  876.         var
  877.             TitleRect: Rect;
  878.             Width: Integer;
  879.             ShortTitle: Str255;
  880.     begin
  881.         TitleRect := TheRect;
  882.         InsetRect(TitleRect, 2, 2);
  883.         TitleRect.right := TitleRect.right - 20;
  884.         EraseRect(TitleRect);
  885.         Width := TitleRect.Right - TitleRect.Left;
  886.         ShortTitle := EllipsisString(TitleString, Width);
  887.         MoveTo(theRect.Left + 4, theRect.Bottom - 5);
  888.         DrawString(ShortTitle);
  889.     end;
  890.  
  891.  
  892.     function DoDialogPopUp (WhichDialog: DialogPtr; TitleDItem, PopUpDItem, StartSelection: Integer; theMenu: MenuHandle): Integer;
  893.         var
  894.             MenuPoint: Point;
  895.             Result: Integer;
  896.             TheRect: Rect;
  897.     begin
  898.         SetPort(WhichDialog);
  899.         InvertRect(GetDRect(WhichDialog, TitleDItem));
  900.         MenuPoint := GetDRect(WhichDialog, PopUpDItem).TopLeft;
  901.         LocalToGlobal(MenuPoint);
  902.         Result := PopUpMenuSelect(TheMenu, MenuPoint.v, MenuPoint.h, StartSelection);
  903.         InvertRect(GetDRect(WhichDialog, TitleDItem));
  904.         TheRect := GetDRect(WhichDialog, PopUpDItem);
  905.         InsetRect(TheRect, 2, 2);
  906.         InvalRect(TheRect);
  907.         DoDialogPopUp := Result;
  908.     end;
  909.  
  910.  
  911.     procedure PlotAnyIcon (Icon: Handle; Mask: Ptr; Where: Rect);
  912.     begin
  913. {This routine glances at the ScreenDepth and determines from that what kind of icon this is.}
  914. {It transfers this icon to the current port, using the b/w mask as a mask.}
  915.     end;
  916.  
  917.  
  918. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  919.  
  920. {Create a new list with no elements.}
  921.     procedure CreateList;
  922.         const
  923.             StandardList = 0;
  924.         var
  925.             ViewRect: Rect;
  926.             DataBounds: Rect;
  927.             CellSize: Point;
  928.             TempInteger: Integer;  {Just to do a little math}
  929.     begin
  930. {Inset the box to make room for the scroll bar.  Also inset it so we've got room for a border.}
  931.         ViewRect := TheRect;
  932.         InsetRect(ViewRect, 1, 1);
  933.         ViewRect.Right := ViewRect.Right - 15;
  934. {Set the cell size to the size of the cell}
  935.         CellSize.v := TheWindow^.txSize + 3;
  936.         if CellSize.v = 3 then  {If it hasn't been set, then make it 12 point.}
  937.             begin
  938.                 TextSize(12);
  939.                 CellSize.v := 15;
  940.             end;
  941.         CellSize.h := ViewRect.Right - ViewRect.Left;
  942. {Now adjust the ViewRect to avoid cutting off the last visible cell}
  943.         TempInteger := (ViewRect.Bottom - ViewRect.Top) div CellSize.v;
  944.         ViewRect.Bottom := ViewRect.Top + (TempInteger * CellSize.v);
  945. {Create the new list.}
  946.         SetRect(DataBounds, 0, 0, 1, 0);
  947.         TheListHandle := LNew(ViewRect, DataBounds, CellSize, StandardList, TheWindow, FALSE, FALSE, FALSE, TRUE);
  948.         UpdateList(TheListHandle);
  949.     end;
  950.  
  951. {Update the art of a list.}
  952.     procedure UpdateList;
  953.         var
  954.             ViewRect: Rect;
  955.             ListUpdateRgn: RgnHandle;
  956.     begin
  957.         SetPort(TheListHandle^^.Port);
  958. {Get the List manager to update the list.}
  959.         ViewRect := TheListHandle^^.rView;
  960.         LDoDraw(true, TheListHandle);
  961.         ListUpdateRgn := NewRgn;
  962.         RectRgn(ListUpdateRgn, ViewRect);
  963.         LUpdate(ListUpdateRgn, TheListHandle);
  964. {Draw the border}
  965.         InsetRect(ViewRect, -1, -1);
  966.         FrameRect(ViewRect);
  967. {Clean up after ourselves}
  968.         DisposeRgn(ListUpdateRgn);
  969.     end;
  970.  
  971. {Handle a click in the list rectangle.  If it was a double-click, we will return TRUE.}
  972.     function DoClick;
  973.     begin
  974.         SetPort(TheListHandle^^.Port);
  975.         LDoDraw(TRUE, TheListHandle);
  976.         DoClick := LClick(TheWhere, 0, TheListHandle);
  977.     end;
  978.  
  979. {Turn off any hilited item.}
  980.     procedure TurnOffSelection;
  981.         var
  982.             ResultPoint: Point;
  983.     begin
  984.         SetPt(ResultPoint, 0, 0);
  985.         if LGetSelect(TRUE, ResultPoint, TheListHandle) then
  986.             LSetSelect(FALSE, ResultPoint, TheListHandle);
  987.     end;
  988.  
  989. {Return the string currently selected; empty string if no selection.}
  990.     function ListSelection;
  991.         var
  992.             ResultPoint: Point;
  993.             ResultString: Str255;
  994.             StringPointer: Ptr;
  995.             StringLength: Integer;
  996.     begin
  997.         SetPt(ResultPoint, 0, 0);
  998.         if LGetSelect(TRUE, ResultPoint, TheListHandle) then
  999. {If there is a cell selected, then get the string value of that string.  There ought to be an}
  1000. {easier way to do this than mucking around in the memory like this.  >:-(    }
  1001.             begin  {If there is a cell selected, then return the string of the cell.}
  1002.                 StringPointer := Ptr(Ord(@ResultString) + 1);
  1003.                 StringLength := 255;  {This is the maximum amount of data we are allowed to move.}
  1004.                 LGetCell(StringPointer, StringLength, ResultPoint, TheListHandle);
  1005.                 StringPointer := Ptr(Ord(@ResultString));
  1006.                 StringPointer^ := StringLength;
  1007.                 ListSelection := ResultString;
  1008.             end
  1009.         else  {Otherwise, return the empty string to show that nothing is selected.}
  1010.             ListSelection := '';
  1011.     end;
  1012.  
  1013. {Add a new cell containing the string parameter to the end of the list}
  1014.     procedure AddCell;
  1015.         var
  1016.             Counter: Integer;
  1017.             CellPoint: Point;
  1018.             OldString: Str255;
  1019.             CompResult: Integer;
  1020.             StringLength: Integer;
  1021.             StringPointer: Ptr;
  1022.             done: Boolean;
  1023.     begin
  1024. {Step 1:  Circle through the loop and figure out where we should insert the new}
  1025. {cell.  We do this to put the list in alphabetical order, and to keep it that way as}
  1026. {new objects are added.}
  1027.         CellPoint.h := 0;
  1028.         CellPoint.v := 0;
  1029.         Done := false;
  1030.         while not done do
  1031.             begin
  1032.                 if LNextCell(TRUE, TRUE, CellPoint, TheListHandle) then
  1033.                     begin
  1034.                         StringPointer := Ptr(Ord(@OldString) + 1);
  1035.                         StringLength := 255;  {This is the maximum amount of data we are allowed to move.}
  1036.                         LGetCell(StringPointer, StringLength, CellPoint, TheListHandle);
  1037.                         StringPointer := Ptr(Ord(@OldString));
  1038.                         StringPointer^ := StringLength;
  1039.  
  1040.                         CompResult := RelString(NewString, OldString, false, true);
  1041.                         case CompResult of
  1042.                             sortsBefore, sortsEqual: 
  1043.                                 done := true;
  1044.                             SortsAfter: 
  1045.                                 ;  {Try again!}
  1046.                         end;
  1047.                     end
  1048.                 else
  1049. {There are no more rows, so that's all.}
  1050.                     begin
  1051.                         done := true;
  1052.                     end;
  1053.             end;
  1054.  
  1055. {Add the new row at the top of the list.}
  1056.         CellPoint.v := LAddRow(1, CellPoint.v, TheListHandle);
  1057. {Put the string into the cell.  Once again, there ought to be an easier way to do this.}
  1058.         LSetCell(Pointer(Ord(@NewString) + 1), Length(NewString), CellPoint, TheListHandle);
  1059.     end;
  1060.  
  1061.  
  1062.     procedure RenameCell;
  1063.         var
  1064.             CellPoint: Point;
  1065.             DataPtr: Ptr;
  1066.             DataLen: Integer;
  1067.     begin
  1068.         SetPt(CellPoint, 0, 0);
  1069.         DataPtr := Pointer(Ord(@OldString) + 1);
  1070.         dataLen := Length(OldString);
  1071.         if LSearch(dataPtr, dataLen, nil, CellPoint, TheListHandle) then
  1072.             begin
  1073.                 DataPtr := Pointer(Ord(@NewString) + 1);
  1074.                 dataLen := Length(NewString);
  1075.                 LSetCell(DataPtr, dataLen, CellPoint, TheListHandle);
  1076.             end
  1077.         else
  1078.             begin
  1079.                 Sysbeep(1);
  1080.                 Sysbeep(1);
  1081.                 Sysbeep(1);
  1082.             end;
  1083.     end;
  1084.  
  1085.  
  1086. {Remove the cell with the given name from the list.}
  1087.     procedure DeleteCell;
  1088.         var
  1089.             CellPoint: Point;
  1090.             DataPtr: Ptr;
  1091.             DataLen: Integer;
  1092.     begin
  1093.         SetPt(CellPoint, 0, 0);
  1094.         DataPtr := Pointer(Ord(@TheString) + 1);
  1095.         dataLen := Length(TheString);
  1096.         if LSearch(dataPtr, dataLen, nil, CellPoint, TheListHandle) then
  1097.             begin
  1098.                 LDelRow(1, CellPoint.v, TheListHandle);
  1099.             end
  1100.         else
  1101.             begin
  1102.                 Sysbeep(1);
  1103.                 Sysbeep(1);
  1104.                 Sysbeep(1);
  1105.             end;
  1106.     end;
  1107.  
  1108. {Get rid of the list when we're done with it, cleaning up all the memory.}
  1109.     procedure DisposList;
  1110.     begin
  1111.         LDispose(TheListHandle);
  1112.     end;
  1113.  
  1114. {Above is the list manager section.}
  1115. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  1116.  
  1117.  
  1118.  
  1119.     function TrackHalfRect (ThisRect: Rect): Boolean;
  1120. {This routine basically does the same thing as the Toolbox routine TrackControl.}
  1121. {This routine is called when the mouse is clicked in the arrow picture.  It hilites}
  1122. {the half of the arrow clicked.  If the mouse moves out of the picture, the picture}
  1123. {is unhilited.  If it moves back in with button still held down, it is rehilited.}
  1124. {TrackHalfRect is TRUE if and only if the mouse is released in the same rectangle}
  1125. {that it was pushed in.}
  1126.         var
  1127.             IsInRect: Boolean;
  1128.             MouseLoc: Point;
  1129.     begin
  1130.         InvertRect(ThisRect);
  1131.         IsInRect := true;
  1132.         while Button do
  1133.             begin
  1134.                 GetMouse(MouseLoc);
  1135.                 if PtInRect(MouseLoc, ThisRect) then
  1136.                     if IsInRect then
  1137. {Do nothing}
  1138.                     else
  1139.                         begin
  1140.                             IsInRect := true;
  1141.                             InvertRect(ThisRect);
  1142.                         end
  1143.                 else if IsInRect then
  1144.                     begin
  1145.                         IsInRect := false;
  1146.                         InvertRect(ThisRect);
  1147.                     end
  1148.                 else
  1149.             end;
  1150.         if IsInRect then
  1151.             InvertRect(ThisRect);
  1152.         TrackHalfRect := IsInRect;
  1153.     end;
  1154.  
  1155.     function ArrowClick (ClickWhere: Point; ArrowRect: Rect): Integer;
  1156. {This routine handles a click in an up-down arrow 'control'.  It returns 1 if the up-arrow was clicked,}
  1157. {2 if the down-arrow was clicked, and 0 if neither was clicked (i.e., the mouse-up came outside the}
  1158. {control.}
  1159.         var
  1160.             UpRect, DownRect: Rect;
  1161.     begin
  1162. {First, we have to figure out what the rectangles are.}
  1163. {UpRect is set to the upper half of the picture rectangle.}
  1164. {DownRect is set to... well, you get the idea.}
  1165.         UpRect := ArrowRect;
  1166.         DownRect := ArrowRect;
  1167.         UpRect.Bottom := UpRect.Bottom - ((ArrowRect.Bottom - ArrowRect.Top) div 2);
  1168.         DownRect.Top := DownRect.Top + ((ArrowRect.Bottom - ArrowRect.Top) div 2);
  1169.         ArrowClick := 0;
  1170.         if PtInRect(ClickWhere, UpRect) then
  1171.             begin
  1172.                 if TrackHalfRect(UpRect) then
  1173.                     ArrowClick := 1;
  1174.             end;
  1175.         if PtInRect(ClickWhere, DownRect) then
  1176.             begin
  1177.                 if TrackHalfRect(DownRect) then
  1178.                     ArrowClick := 2;
  1179.             end;
  1180.     end;
  1181.  
  1182.  
  1183.  
  1184. end.
  1185.